﻿/* ===================================尊重原著======================================
 * 创 建 者：mao xiao jun 
 * 创建日期：2021-12-12
 * CLR Version : any
 * ===================================尊重原著=====================================*/
using MTP.Serialize;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;



namespace MTP.Serialize
{
    /// <summary>
    /// 
    /// </summary>
    public class MTPSerialize
    {
        private static readonly Type StringType = typeof(string);

        private static readonly Type EnumType = typeof(Enum);

        private static readonly Type IEnumerableType = typeof(IEnumerable);

        private static readonly Type IFormatProviderType = typeof(IFormatProvider);

        private static readonly Type IFormattableType = typeof(IFormattable);

        private static readonly Type IConvertibleType = typeof(IConvertible);

        private static readonly Type NullableType = typeof(Nullable<>);
        private static readonly Type Int64Type = typeof(long);
        private static readonly Type Int32Type = typeof(int);
        private static readonly Type Int16Type = typeof(short);
        private static readonly Type ByteType = typeof(byte);
        private static readonly Type BoolType = typeof(bool);
        private static readonly Type FloatType = typeof(float);
        private static readonly Type DoubleType = typeof(double);
        private static readonly Type DateTimeType = typeof(DateTime);
        private static readonly Type DateTimeOffsetType = typeof(DateTimeOffset);
        private static readonly CultureInfo DefaultCultureInfo = new CultureInfo("zh-cn");

        private static string GetString(byte[] bytes)
        {
            return Encoding.UTF8.GetString(bytes);
        }

        private static byte[] StringToBytes(string str)
        {
            return Encoding.UTF8.GetBytes(str);
        }
        /// <summary>
        /// 序列化
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static byte[] Serialize(object obj)
        {
            return SerializeAny(obj);
        }


        /// <summary>
        /// 序列化组
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public static byte[] SerializeArray(params object[] array)
        {
            List<byte[]> result = new List<byte[]>();
            foreach (var item in array)
            {
                result.Add(SerializeAny(item));
            }
            return result.SelectMany(a => a).ToArray();
        }

        public static Stream SerializeStreamArray(params object[] array)
        {
            return new MemoryStream(SerializeArray(array));
        }

        /// <summary>
        /// 反序列化组
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="types"></param>
        /// <returns></returns>
        public static object[] DeserializeArray(byte[] bytes, params Type[] types)
        {
            try
            {
                long offset = 0;
                long length = bytes.Length;
                ArrayList arrayList = new ArrayList();
                int i = 0;
                while (offset < length)
                {
                    arrayList.Add(DeserializeAny(bytes, types[i], ref offset));
                    i++;
                }
                return arrayList.ToArray();
            }
            catch (Exception)
            {
                throw new FormatException("无法解析该类型");
            }
        }
        /// <summary>
        /// 分割序列化的数据
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static List<byte[]> DeserializeSplit(Stream stream)
        {
            using MemoryStream memoryStream = new MemoryStream();
            stream.CopyTo(memoryStream);
            return DeserializeSplit(memoryStream.ToArray());

        }

        /// <summary>
        /// 分割序列化的数据
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static List<byte[]> DeserializeSplit(byte[] bytes)
        {
            try
            {
                long offset = 0;
                long length = bytes.Length;
                List<byte[]> arrayList = new List<byte[]>();
                int i = 0;
                while (offset < length)
                {
                    long start = offset;
                    _ = NextBytes(bytes, out _, ref offset);
                    long dataLength = offset - start;
                    var data = new byte[dataLength];
                    Array.Copy(bytes, start, data, 0, dataLength);
                    arrayList.Add(data);
                    i++;
                }
                return arrayList;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                throw new FormatException("无法解析该类型");
            }
        }

        /// <summary>
        /// 反序列化
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public static object Deserialize(byte[] bytes, Type type)
        {
            long offset = 0;
            return DeserializeAny(bytes, type, ref offset);
        }

        /// <summary>
        /// 反序列化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static T Deserialize<T>(byte[] bytes)
        {
            return (T)Deserialize(bytes, typeof(T));
        }


        public static T DeserializeAnonymous<T>(byte[] bytes, T _)
        {
            return (T)Deserialize(bytes, typeof(T));
        }
        /// <summary>
        /// 反序列化下一个数据
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="headerFirst"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        private static byte[] NextBytes(byte[] bytes, out byte headerFirst, ref long offset)
        {
            byte header = bytes[offset];
            headerFirst = (byte)(header >> 4 << 4);
            offset++;
            switch (headerFirst)
            {
                case MTPDataType.Null:
                    return null;
                case MTPDataType.Number:
                case MTPDataType.Digit:
                case MTPDataType.Unsigned:
                case MTPDataType.Date:
                case MTPDataType.Bool:
                    return GetNumberByBytes(bytes, header, headerFirst, ref offset);
                default:
                    return GetDataBytes(bytes, header, headerFirst, ref offset);
            }
        }

        private static object DeserializeAny(byte[] bytes, Type type, ref long offset)
        {
            var datas = NextBytes(bytes, out byte dataType, ref offset);
            return Deserialize(type, datas, dataType);
        }
        /// <summary>
        /// 反序列化为 type 类型对象
        /// </summary>
        /// <param name="type"></param>
        /// <param name="datas"></param>
        /// <returns></returns>
        private static object Deserialize(Type type, byte[] datas, byte dataType)
        {
            if (datas == null) return null;
            if (type.IsByRef)
            {
                type = type.GetElementType();
            }
            if (type.IsValueType && type.IsGenericType && type.GetGenericTypeDefinition() == NullableType)
            {
                type = Nullable.GetUnderlyingType(type);
            }
            if (type.IsValueType && type.IsPrimitive)
            {
                return ToNumber(datas, type, dataType);
            }
            if (type == DateTimeType)
            {
                return DateTimeOffset.FromUnixTimeMilliseconds((long)ToNumber(datas, Int64Type, dataType)).LocalDateTime;
            }
            if (type == DateTimeOffsetType)
            {
                return DateTimeOffset.FromUnixTimeMilliseconds((long)ToNumber(datas, Int64Type, dataType));
            }
            if (EnumType.IsAssignableFrom(type))
            {
                return Enum.Parse(type, GetString(datas));
            }
            if (IConvertibleType.IsAssignableFrom(type))
            {
                return Convert.ChangeType(GetString(datas), type, DefaultCultureInfo);
            }
            if (IFormattableType.IsAssignableFrom(type))
            {
                return ParseFormattable(type, GetString(datas));
            }
            if (IEnumerableType.IsAssignableFrom(type))
            {
                if (type.FullName.StartsWith("Newtonsoft.Json.Linq.J"))
                {
                    string str = GetString(datas);
                    return Parse(type, str);
                }


                return DeserializeArray(type, datas, dataType);
            }
            //if (type.IsGenericType && KeyValuePair2Type == type.GetGenericTypeDefinition())
            //{
            //    return DeserializeKeyValue(type, datas);
            //}
            return DeserializeClassFactory(type)(datas);
        }

        private static Func<byte[], object> DeserializeGenericType(Type type)
        {
            if (type.IsValueType && type.IsGenericType && type.GetGenericTypeDefinition() == NullableType)
            {
                type = Nullable.GetUnderlyingType(type);
            }
            if ((type.IsValueType && type.IsPrimitive) || type == DateTimeType || type == DateTimeOffsetType)
            {
                return null;
            }
            if (IConvertibleType.IsAssignableFrom(type))
            {
                return (datas) =>
                {
                    if (datas == null) return null;
                    string str = GetString(datas);
                    return Convert.ChangeType(str, type, DefaultCultureInfo);
                };
            }
            if (IFormattableType.IsAssignableFrom(type))
            {
                return (datas) =>
                {
                    if (datas == null) return null;
                    string str = GetString(datas);
                    return ParseFormattable(type, str);
                };
            }
            if (IEnumerableType.IsAssignableFrom(type))
            {
                return null;
            }

            return DeserializeClassFactory(type);

        }

        private static byte[] SerializeAny(object obj)
        {
            if (obj == null)
            {
                return new byte[] { MTPDataType.Null };
            }

            var type = obj.GetType();
            if (type.IsValueType && type.IsPrimitive)
            {
                return BuilderNumberBytes(obj);
            }
            if (obj is DateTime dt)
            {
                if (dt == DateTime.MinValue)
                {
                    return BuilderNumberBytes(DateTimeOffset.MinValue.ToUnixTimeMilliseconds(), MTPDataType.Date);
                }
                else if (dt == DateTime.MaxValue)
                {
                    return BuilderNumberBytes(DateTimeOffset.MaxValue.ToUnixTimeMilliseconds(), MTPDataType.Date);
                }
                return BuilderNumberBytes(new DateTimeOffset(dt).ToUnixTimeMilliseconds(), MTPDataType.Date);
            }
            if (obj is DateTimeOffset dto)
            {
                return BuilderNumberBytes(dto.ToUnixTimeMilliseconds(), MTPDataType.Date);
            }
            if (EnumType.IsAssignableFrom(type))
            {
                return BuilderBytes(obj.ToString());
            }
            if (IConvertibleType.IsAssignableFrom(type) || IFormattableType.IsAssignableFrom(type))
            {
                return BuilderBytes(Convert.ToString(obj, DefaultCultureInfo));
            }
            if (IEnumerableType.IsAssignableFrom(type))
            {
                if (type.FullName.StartsWith("Newtonsoft.Json.Linq.J"))
                {
                    return SerializeAny(obj.ToString());
                }
                return SerializeArray(obj as IEnumerable);
            }
            return SerializeClass(type, obj);
        }
        /// <summary>
        /// 带有 Formattable 反系列化为对象
        /// </summary>
        /// <param name="type"></param>
        /// <param name="str"></param>
        /// <returns></returns>
        private static object ParseFormattable(Type type, string str)
        {
            var method = type.GetMethod("Parse", new Type[] { StringType, IFormatProviderType });
            if (method == null)
            {
                return ParseValue(type, str);
            }
            return type.GetMethod("Parse", new Type[] { StringType, IFormatProviderType }).Invoke(null, new object[] { str, DefaultCultureInfo });
        }

        private static object ParseValue(Type type, string str)
        {
            var arr = str.Replace("(", "").Replace(")", "").Split(',', StringSplitOptions.RemoveEmptyEntries);
            var constructor = type.GetConstructors().FirstOrDefault(a => a.GetParameters().Length == arr.Length);
            if (constructor == null)
            {
                return null;
            }
            var args = constructor.GetParameters().Select((a, i) => Parse(a.ParameterType, arr[i])).ToArray();
            return constructor.Invoke(args);
        }

        private static object Parse(Type type, string str)
        {
            return type.GetMethod("Parse", new Type[] { StringType }).Invoke(null, new object[] { str });
        }


        private static byte[] SerializeClass(Type type, object obj)
        {
            if (type.FullName.StartsWith("System.Reflection."))
            {
                return SerializeAny(null);
            }
            var ps = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            var fs = type.GetFields(BindingFlags.Instance | BindingFlags.Public);
            return SerializeClass(ps.Where(a => a.CanRead).ToArray(), fs, obj);
        }

        private static Func<byte[], object> DeserializeClassFactory(Type type)
        {
            var constructors = type.GetConstructors();
            if (constructors.Length == 0)
                throw new InvalidConstraintException("Missing constructor " + type.FullName);

            var parameters = constructors[0].GetParameters();
            var ps = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            return (bytes) =>
            {
                if (bytes == null) return null;
                var dict = ResolveClass(bytes);
                var obj = LoadFromConstructor(type, parameters, dict);

                return LoadFromPropertyInfos(obj, ps, dict);
            };
        }


        private static object LoadFromPropertyInfos(object model, PropertyInfo[] parameters, Dictionary<string, (byte, byte[])> dict)
        {
            foreach (var propertyInfo in parameters)
            {
                if (propertyInfo.CanWrite && dict.TryGetValue(propertyInfo.Name, out (byte dateType, byte[] bytes) item))
                {
                    var value = Deserialize(propertyInfo.PropertyType, item.bytes, item.dateType);
                    if (value != null)
                        propertyInfo.SetValue(model, value);
                }
            }
            return model;
        }

        private static object LoadFromConstructor(Type type, ParameterInfo[] parameters, Dictionary<string, (byte, byte[])> dict)
        {
            return Activator.CreateInstance(type, parameters.Select(parameter =>
            {
                if (dict.TryGetValue(parameter.Name, out (byte dateType, byte[] bytes) item))
                {
                    return Deserialize(parameter.ParameterType, item.bytes, item.dateType);
                }
                return null;
            }).ToArray());
        }

        private static Dictionary<string, (byte, byte[])> ResolveClass(byte[] datas)
        {
            long offset = 0;
            long length = datas.Length;
            Dictionary<string, (byte, byte[])> dict = new Dictionary<string, (byte, byte[])>(StringComparer.OrdinalIgnoreCase);
            while (offset < length)
            {
                string name = GetNameByBytes(datas, ref offset);
                var bytes = NextBytes(datas, out byte dataType, ref offset);
                if (bytes != null)
                    dict.Add(name, (dataType, bytes));
            }
            return dict;
        }

        private static byte[] SerializeClass(PropertyInfo[] ps, FieldInfo[] fs, object obj)
        {
            List<byte[]> result = new List<byte[]>();
            foreach (var property in ps)
            {
                var value = property.GetValue(obj);
                if (value != null)
                {
                    result.Add(BuilderNameBytes(property.Name));
                    result.Add(SerializeAny(value));
                }
            }
            foreach (var property in fs)
            {
                if (ps.Any(b => b.Name == property.Name)) continue;
                var value = property.GetValue(obj);
                if (value != null)
                {
                    result.Add(BuilderNameBytes(property.Name));
                    result.Add(SerializeAny(value));
                }
            }
            var datas = result.SelectMany(a => a).ToArray();
            return BuilderBytes(MTPDataType.Class, datas);
        }

        private static Type GetElementType(Type arrayType)
        {
            if (arrayType.IsArray)
            {
                return arrayType.GetElementType();
            }
            if (arrayType.IsGenericType)
            {
                if (arrayType.GetGenericArguments().Length == 1)
                {
                    return arrayType.GetGenericArguments()[0];
                }
            }
            return null;


        }
        private static byte[] SerializeArray(IEnumerable array)
        {
            var type = GetElementType(array.GetType());
            var dataType = MTPDataType.Array;
            List<byte[]> result = new List<byte[]>();
            if (type != null && type.IsPrimitive && type.IsValueType)
            {
                dataType = MTPDataType.ValueArray;

                foreach (var item in array)
                {
                    var bs = GetBytes(item, out _);
                    if (result.Count == 0)
                    {
                        result.Add(new byte[] { (byte)bs.Length });
                    }
                    result.Add(bs);
                }
                var datas = result.SelectMany(a => a).ToArray();
                return BuilderBytes(dataType, datas);
            }
            else
            {

                foreach (var item in array)
                {
                    result.Add(SerializeAny(item));
                }
                var datas = result.SelectMany(a => a).ToArray();
                return BuilderBytes(dataType, datas);
            }


        }
        private static List<byte[]> SplitArray(byte[] datas, int parts, int offset, int length)
        {
            length = Math.Min(datas.Length, length);
            List<byte[]> result = new List<byte[]>();
            for (int i = offset; i < length; i += parts)
            {
                byte[] item = new byte[parts];
                Array.Copy(datas, i, item, 0, parts);
                result.Add(item);
            }
            return result;
        }


        private static object GetValueArray(Type type, Type elementType, byte[] datas)
        {

            if (datas.Length > 0)
            {
                ArrayList arrayList = new ArrayList();
                var wl = datas[0];

                var bytes = SplitArray(datas, wl, 1, datas.Length);

                foreach (var item in bytes)
                {
                    arrayList.Add(ToNumber(item, elementType, 0));
                }
                if (type.IsArray)
                {
                    return arrayList.ToArray(elementType);
                }
                return Activator.CreateInstance(type, arrayList.ToArray(elementType));
            }
            else
            {
                if (type.IsArray)
                {
                    return Array.CreateInstance(elementType, 0);
                }
                return Activator.CreateInstance(type);
            }

        }
        private static object DeserializeArray(Type type, byte[] datas, int dataType)
        {
            if (dataType == MTPDataType.ValueArray)
            {
                var elementType = GetElementType(type);
                if (elementType.IsValueType && elementType.IsPrimitive)
                {
                    return GetValueArray(type, elementType, datas);
                }
            }
            long offset = 0;
            long length = datas.Length;
            ArrayList arrayList = new ArrayList();
            var genericType = type.GetInterface("IEnumerable`1").GetGenericArguments()[0];
            var func = DeserializeGenericType(genericType);
            while (offset < length)
            {
                if (func != null)
                {
                    var data = NextBytes(datas, out _, ref offset);
                    arrayList.Add(func(data));
                }
                else
                {
                    arrayList.Add(DeserializeAny(datas, genericType, ref offset));
                }
            }
            if (type.IsArray)
            {
                return arrayList.ToArray(genericType);
            }
            return Activator.CreateInstance(type, arrayList.ToArray(genericType));

        }
        /// <summary>
        /// 转名称
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        private static string GetNameByBytes(byte[] bytes, ref long offset)
        {
            int length = bytes[offset];
            offset++;
            byte[] strBytes = new byte[length];
            Array.Copy(bytes, offset, strBytes, 0, length);
            offset += length;
            return GetString(strBytes);
        }

        private static byte[] GetDataBytes(byte[] bytes, byte header, int headerFirst, ref long offset)
        {
            int next = header ^ headerFirst;
            var lengthBytes = new byte[next];
            Array.Copy(bytes, offset, lengthBytes, 0, next);
            var length = ToInt64(lengthBytes);
            offset += next;
            var dataBytes = new byte[length];
            Array.Copy(bytes, offset, dataBytes, 0, length);
            offset += length;
            return dataBytes;
        }

        private static byte[] GetNumberByBytes(byte[] bytes, byte header, int headerFirst, ref long offset)
        {
            int next = header ^ headerFirst;
            var datas = new byte[next];
            Array.Copy(bytes, offset, datas, 0, next);
            offset += next;
            return datas;
        }

        /// <summary>
        /// 名称 255 个字符以内
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private static byte[] BuilderNameBytes(string name)
        {
            var data = StringToBytes(name);
            byte[] bytes = new byte[data.Length + 1];
            bytes[0] = (byte)data.Length;
            Array.Copy(data, 0, bytes, 1, data.Length);
            return bytes;
        }

        private static byte[] BuilderBytes(string data)
        {
            return BuilderBytes(MTPDataType.String, StringToBytes(data));
        }

        #region 基础操作


        private static byte[] GetBytes(object data, out byte dataType)
        {
            dataType = MTPDataType.Number;
            if (data is long lon)
            {
                return BitConverter.GetBytes(lon);
            }
            if (data is long ulon)
            {
                dataType = MTPDataType.Unsigned;
                return BitConverter.GetBytes(ulon);
            }
            if (data is int @int)
            {
                return BitConverter.GetBytes(@int);
            }
            if (data is int @uint)
            {
                dataType = MTPDataType.Unsigned;
                return BitConverter.GetBytes(@uint);
            }
            if (data is short sho)
            {
                return BitConverter.GetBytes(sho);
            }
            if (data is ushort usho)
            {
                dataType = MTPDataType.Unsigned;
                return BitConverter.GetBytes(usho);
            }
            if (data is char cha)
            {
                dataType = MTPDataType.Char;
                return BitConverter.GetBytes(cha);
            }
            if (data is sbyte sbyt)
            {
                return new byte[] { (byte)(sbyt & 0xff) };
            }
            if (data is byte byt)
            {
                dataType = MTPDataType.Unsigned;
                return new byte[] { byt };
            }
            if (data is bool boo)
            {
                dataType = MTPDataType.Bool;
                return BitConverter.GetBytes(boo);
            }
            if (data is double dou)
            {
                dataType = MTPDataType.Digit;
                return BitConverter.GetBytes(dou);
            }
            if (data is float flo)
            {
                dataType = MTPDataType.Digit;
                return BitConverter.GetBytes(flo);
            }
            return new byte[0];
        }

        private static object ToNumber(byte[] bytes, Type type, byte dataType)
        {
            if (type == Int64Type)
            {
                return BitConverter.ToInt64(bytes);
            }
            if (type == typeof(ulong))
            {
                return BitConverter.ToUInt64(bytes);
            }
            if (type == Int32Type)
            {
                return BitConverter.ToInt32(bytes);
            }
            if (type == typeof(uint))
            {
                return BitConverter.ToUInt32(bytes);
            }
            if (type == Int16Type)
            {
                return BitConverter.ToInt16(bytes);
            }
            if (type == typeof(ushort))
            {
                return BitConverter.ToUInt16(bytes);
            }
            if (type == typeof(char))
            {
                return BitConverter.ToChar(bytes);
            }
            if (type == typeof(sbyte))
            {
                return (sbyte)bytes[0];
            }
            if (type == ByteType)
            {
                return bytes[0];
            }
            if (type == BoolType)
            {
                return bytes[0] > 0;
            }
            if (type == DoubleType)
            {
                return BitConverter.ToDouble(bytes);
            }
            if (type == FloatType)
            {
                return BitConverter.ToSingle(bytes);
            }
            return Convert.ChangeType(0, type);
        }
        #endregion


        private static byte[] BuilderNumberBytes(object data, byte? dataType2 = null)
        {
            byte dataType;
            var datas = GetBytes(data, out dataType);
            dataType = dataType2 ?? dataType;
            byte dataLength = (byte)datas.Length;
            byte[] bytes = new byte[1 + dataLength];
            dataType |= dataLength;
            bytes[0] = dataType;
            Array.Copy(datas, 0, bytes, 1, dataLength);
            return bytes;
        }



        private static byte[] BuilderBytes(byte dataType, byte[] data)
        {

            byte dataLength = GetLengthBit(data.LongLength);

            var headerLenght = 1 + dataLength;

            // long 类型 8位存储内容长度 int 类型 4位
            byte[] bytes = new byte[data.LongLength + headerLenght];

            dataType |= dataLength;

            bytes[0] = dataType;
            var byteLength = ToBytes(data.LongLength, dataLength);

            Array.Copy(byteLength, 0, bytes, 1, (int)dataLength);

            Array.Copy(data, 0, bytes, headerLenght, data.Length);
            return bytes;

        }

        private static byte GetLengthBit(long length)
        {
            byte i = 1;
            while ((length >>= 8) > 0)
            {
                i++;
            }
            return i;
        }

        private static long ToInt64(byte[] bytes)
        {
            return bytes.Select(a => (long)a).Aggregate((a, b) => (a << 8) + b);
        }

        /// <summary>
        /// 转字节数组
        /// </summary>
        /// <param name="value"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        private static byte[] ToBytes(long value, int length = 8)
        {
            var bytes = new byte[length];
            for (int i = 0; i < length; i++)
            {
                // 写入一个字节
                bytes[i] = (byte)(value >> (length - i - 1) * 8 & 0xff);
            }
            return bytes;
        }
    }
}
